package main

// 生成 cd1 的网络，并进行计算 disruption
// 1 年后的计算

import (
	"context"
	"log"
	"mag/internal/database"
	"time"

	"github.com/emirpasic/gods/sets/hashset"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

var (
	yearMap = make(map[int64]int)
)

func main() {
	// 初始化容器
	log.Println("开始导入")
	// rangInMap := make(map[int64]*hashset.Set)
	rangOutMap := make(map[int64][]int64)

	ctx := context.Background()
	client := database.InitMongo()
	collection := client.Database("mag2020").Collection("pageinfo")

	cur, err := collection.Find(ctx, bson.M{})
	if err != nil {
		log.Fatal(err)
	}
	defer cur.Close(ctx)

	for cur.Next(ctx) {

		var result database.PageInfo
		err := cur.Decode(&result)
		if err != nil {
			log.Fatal(err)
		}
		// 加入 year
		yearMap[result.Id] = result.Year

		// 开始过滤一遍
		if len(result.Out) == 0 && len(result.In) == 0 {
			continue
		}

		// 初始化 out data
		// rangOutMap[result.Id] = hashset.New()

		for _, item := range result.Out {
			// rangOutMap[result.Id].Add(item)
			rangOutMap[result.Id] = append(rangOutMap[result.Id], item)
			// if rangInMap[item] == nil {
			// 	rangInMap[item] = hashset.New()
			// }
			// rangInMap[item].Add(result.Id)
		}
	}

	// 进行 cd1 过滤
	cd1_rangInMap := make(map[int64]*hashset.Set)
	cd1_rangOutMap := make(map[int64]*hashset.Set)

	for key, items := range rangOutMap {
		hset := hashset.New()
		for _, out_key := range items {
			if yearMap[out_key]+1 <= yearMap[key] {
				hset.Add(out_key)
				if cd1_rangInMap[out_key] == nil {
					cd1_rangInMap[out_key] = hashset.New()
				}
				cd1_rangInMap[out_key].Add(key)
			}
		}
		if hset.Size() > 0 {
			cd1_rangOutMap[key] = hset
		}

	}

	log.Println("数据导入完毕，开始计算 D")
	// 开始计算 disruption
	outchan := calDisruption(cd1_rangOutMap, cd1_rangInMap)
	// go handleOutChan(outchan, "disruption_cd1")
	handleOutChan(outchan, "disruption_cd1")

	log.Println("over")
	time.Sleep(99999)
}

type disruptionItem struct {
	ID   int64   `bson:"_id"`
	D    float64 `bson:"d"`
	I    int64   `bson:"i"`
	J    int64   `bson:"j"`
	K    int64   `bson:"k"`
	Year int     `bson:"y"`
}

func (c *disruptionItem) CalD() {
	c.D = (float64(c.I) - float64(c.J)) / (float64(c.I) + float64(c.J) + float64(c.K))
}

func calDisruption(outMap, inMap map[int64]*hashset.Set) chan *disruptionItem {
	outChan := make(chan *disruptionItem, 10000)
	go func() {
		for key, outSet := range outMap {
			retData := disruptionItem{ID: key}
			retData.Year = yearMap[key]

			// 获取 linksin set, 如果没有了，就跳过
			inSet := inMap[key]
			if inSet == nil || outSet.Size() == 0 {
				continue
			}

			outInSet := hashset.New()

			for _, item := range outSet.Values() {
				if outinset, ok := inMap[item.(int64)]; ok {
					outInSet.Add(outinset.Values()...)
				}
			}

			// 计算颠覆度参数
			for _, nodeID := range inSet.Values() {
				nodeIDOutSet := outMap[nodeID.(int64)]
				if Is_intersection(nodeIDOutSet, outSet) {
					retData.J += 1
				} else {
					retData.I += 1
				}
			}
			// 排除自身节点
			outInSet.Remove(key)
			retData.K = Cal_uniq_count(outInSet, inSet)
			retData.CalD()
			outChan <- &retData
		}
		close(outChan)
	}()
	return outChan
}

func handleOutChan(outchan chan *disruptionItem, collectionName string) {

	collection := database.InitMongo().Database("mag2020").Collection(collectionName)

	opts := options.BulkWrite().SetOrdered(false)
	models := []mongo.WriteModel{}
	i := 0
	for item := range outchan {
		i += 1
		models = append(models, mongo.NewInsertOneModel().SetDocument(item))
		if i%10000 == 0 {
			retTmp, err := collection.BulkWrite(context.Background(), models, opts)
			if err != nil {
				log.Println("bulk upsert fail", err)
				log.Printf("%+v", retTmp)
			}
			models = []mongo.WriteModel{}
		}
	}
	if len(models) > 0 {
		retTmp, err := collection.BulkWrite(context.Background(), models, opts)
		if err != nil {
			log.Println("bulk upsert fail", err)
			log.Printf("%+v", retTmp)
		}
	}
}

// 计算是否有交集
func Is_intersection(a, b *hashset.Set) bool {
	for _, v := range a.Values() {
		if b.Contains(v.(int64)) {
			return true
		}
	}
	return false
}

// 计算差
func Cal_uniq_count(a, b *hashset.Set) (count int64) {
	for _, v := range a.Values() {
		if !b.Contains(v.(int64)) {
			count += 1
		}
	}
	return
}
